Added logic for keeping background process runing on closing of debug…#299
Added logic for keeping background process runing on closing of debug…#299rohitneharabrowserstack wants to merge 2 commits intomasterfrom
Conversation
WalkthroughAdds controlled background-window lifecycle handling: startBackgroundProcess.js installs a close handler that hides the window, overrides and preserves the original Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/actions/cleanup.js (1)
17-49:⚠️ Potential issue | 🟡 Minor
ipcMain.once("shutdown-success")listener is never removed on the timeout path.If the timeout fires first (because the background process never sends
"shutdown-success"), the promise resolves but theipcMain.once("shutdown-success")listener remains registered. If a stale"shutdown-success"message arrives later (e.g., from a new background window in a subsequent session), it will invoke the old callback on a now-destroyed window reference.Remove the listener when the timeout fires.
Sketch
+ const onShutdownSuccess = () => { + clearTimeout(shutdownTimeout); + // ... destroy + resolve + }; + ipcMain.once("shutdown-success", onShutdownSuccess); + const shutdownTimeout = setTimeout(() => { + ipcMain.removeListener("shutdown-success", onShutdownSuccess); // ... destroy + resolve }, 2000);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/actions/cleanup.js` around lines 17 - 49, The ipcMain.once("shutdown-success") handler registered in getReadyToQuitApp can remain after the timeout fires; change the logic to capture the listener callback in a variable (the function passed to ipcMain.once) and, inside the timeout fallback before resolving, remove that listener (using ipcMain.removeListener or ipcMain.off with the same event name and captured callback) so the stale callback cannot run later against global.backgroundWindow; keep the existing destroy/_originalDestroy calls and resolve behavior intact.
🧹 Nitpick comments (3)
src/main/actions/startBackgroundProcess.js (3)
72-120: Consider extracting lifecycle protection into a helper function.The close handler, destroy override, and removeAllListeners override collectively form a single "protect window from destruction" concern spread across ~50 lines in the middle of
startBackgroundProcess. Extracting this into a named helper (e.g.,protectWindowFromDestruction(window)) would improve readability and make it testable independently.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/actions/startBackgroundProcess.js` around lines 72 - 120, Extract the window-lifecycle protection logic into a helper named protectWindowFromDestruction(window): move the closeHandler, its attachment, the _preventCloseHandler assignment, the overridden destroy (using originalDestroy and _originalDestroy), and the overridden removeAllListeners (using originalRemoveAllListeners) into that function; have startBackgroundProcess call protectWindowFromDestruction(backgroundWindow) before setupIPCForwardingToBackground and before setting the global flags so behavior is unchanged but the lifecycle-protection concern is isolated and testable.
72-80:event.returnValue = falseis unnecessary in acloseevent handler.In Electron,
event.preventDefault()on thecloseevent is sufficient to prevent the window from closing.event.returnValueis only meaningful in synchronous IPC orbeforeunloadhandlers. Line 75 can be removed for clarity.Proposed fix
const closeHandler = (event) => { event.preventDefault(); - event.returnValue = false; if (!backgroundWindow.isDestroyed()) { backgroundWindow.hide(); } - return false; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/actions/startBackgroundProcess.js` around lines 72 - 80, The close handler sets event.returnValue which is unnecessary for the Electron 'close' event; update the closeHandler (the function assigned where backgroundWindow is handled) to remove the line setting event.returnValue and rely solely on event.preventDefault() and backgroundWindow.hide(); keep the checks for backgroundWindow.isDestroyed() and the final return false if desired, but delete the redundant event.returnValue assignment to simplify the handler.
103-113: Consider a less invasive approach to protecting the close handler.Monkey-patching
removeAllListenersis fragile and brittle across Electron upgrades. WhileremoveListeneris not currently patched or called in the codebase, this approach relies on incomplete protections that could be bypassed.Instead of patching core EventEmitter methods, document that the close handler must not be removed, or refactor to guard the removal at the point where it happens (e.g., skip removing
'close'listeners in any code that callsremoveAllListeners).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/actions/startBackgroundProcess.js` around lines 103 - 113, You currently monkey-patch backgroundWindow.removeAllListeners to protect the closeHandler, which is fragile; instead revert that override and either (A) add a small safe utility or guard at call sites that remove listeners so they skip the 'close' event (e.g., replace calls to backgroundWindow.removeAllListeners(...) with a safeRemoveAllListeners(target, eventName) helper that delegates except when eventName is 'close' or undefined), or (B) centralize listener cleanup in a single function that knows not to remove the 'close' listener; update/remove the override of removeAllListeners and ensure places that previously cleared listeners call the new safe helper or the centralized cleanup, referencing backgroundWindow, closeHandler, removeAllListeners, and removeListener as needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/actions/cleanup.js`:
- Around line 25-45: The shutdown handler for ipcMain.once("shutdown-success")
can race with the timeout and call destroy/resolve twice; store the timeout
handle returned by setTimeout, then in the "shutdown-success" callback first
clearTimeout(timeoutHandle) and check global.backgroundWindow &&
!global.backgroundWindow.isDestroyed() before calling either
global.backgroundWindow._originalDestroy() or global.backgroundWindow.destroy();
likewise keep the existing isDestroyed() guard in the timeout path and only call
resolve() once per path after performing the guarded destroy. Ensure you
reference ipcMain.once("shutdown-success"), the timeout handle from
setTimeout(...), global.backgroundWindow, _originalDestroy, isDestroyed(), and
resolve() when applying the fix.
In `@src/main/actions/startBackgroundProcess.js`:
- Around line 87-98: The overridden destroy implementation calls
originalDestroy() unconditionally when global.allowBackgroundWindowDestruction
is true which can throw if the window is already destroyed; update the
backgroundWindow.destroy override (the function that binds originalDestroy and
replaces backgroundWindow.destroy) to check backgroundWindow.isDestroyed()
before calling originalDestroy(), mirroring the other guard: only call
originalDestroy() when global.allowBackgroundWindowDestruction is true AND
!backgroundWindow.isDestroyed(), otherwise fall back to hiding when not
destroyed.
---
Outside diff comments:
In `@src/main/actions/cleanup.js`:
- Around line 17-49: The ipcMain.once("shutdown-success") handler registered in
getReadyToQuitApp can remain after the timeout fires; change the logic to
capture the listener callback in a variable (the function passed to
ipcMain.once) and, inside the timeout fallback before resolving, remove that
listener (using ipcMain.removeListener or ipcMain.off with the same event name
and captured callback) so the stale callback cannot run later against
global.backgroundWindow; keep the existing destroy/_originalDestroy calls and
resolve behavior intact.
---
Nitpick comments:
In `@src/main/actions/startBackgroundProcess.js`:
- Around line 72-120: Extract the window-lifecycle protection logic into a
helper named protectWindowFromDestruction(window): move the closeHandler, its
attachment, the _preventCloseHandler assignment, the overridden destroy (using
originalDestroy and _originalDestroy), and the overridden removeAllListeners
(using originalRemoveAllListeners) into that function; have
startBackgroundProcess call protectWindowFromDestruction(backgroundWindow)
before setupIPCForwardingToBackground and before setting the global flags so
behavior is unchanged but the lifecycle-protection concern is isolated and
testable.
- Around line 72-80: The close handler sets event.returnValue which is
unnecessary for the Electron 'close' event; update the closeHandler (the
function assigned where backgroundWindow is handled) to remove the line setting
event.returnValue and rely solely on event.preventDefault() and
backgroundWindow.hide(); keep the checks for backgroundWindow.isDestroyed() and
the final return false if desired, but delete the redundant event.returnValue
assignment to simplify the handler.
- Around line 103-113: You currently monkey-patch
backgroundWindow.removeAllListeners to protect the closeHandler, which is
fragile; instead revert that override and either (A) add a small safe utility or
guard at call sites that remove listeners so they skip the 'close' event (e.g.,
replace calls to backgroundWindow.removeAllListeners(...) with a
safeRemoveAllListeners(target, eventName) helper that delegates except when
eventName is 'close' or undefined), or (B) centralize listener cleanup in a
single function that knows not to remove the 'close' listener; update/remove the
override of removeAllListeners and ensure places that previously cleared
listeners call the new safe helper or the centralized cleanup, referencing
backgroundWindow, closeHandler, removeAllListeners, and removeListener as
needed.
There was a problem hiding this comment.
🧹 Nitpick comments (3)
src/main/actions/startBackgroundProcess.js (3)
73-80:event.returnValue = falseandreturn falseare ineffective on BrowserWindow'scloseevent.
event.preventDefault()on line 74 is the correct and sufficient way to cancel a BrowserWindowcloseevent.event.returnValueis meaningful forwebContents'beforeunloadevents, not BrowserWindow lifecycle events, andreturn falsehas no effect in Node EventEmitter handlers. Both can be removed to avoid misleading future readers.Suggested cleanup
const closeHandler = (event) => { event.preventDefault(); - event.returnValue = false; if (!backgroundWindow.isDestroyed()) { backgroundWindow.hide(); } - return false; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/actions/startBackgroundProcess.js` around lines 73 - 80, The closeHandler currently sets event.returnValue = false and returns false which are ineffective for a BrowserWindow 'close' event; update closeHandler to rely solely on event.preventDefault() to cancel the close and then hide the backgroundWindow if it's not destroyed (keep the existing backgroundWindow.hide() call guarded by backgroundWindow.isDestroyed()); remove the lines setting event.returnValue and the final return false so the handler only calls event.preventDefault() and performs the hide action.
103-113:removeListener/offare not overridden, leaving a gap in close-handler protection.If the intent is to prevent external code from stripping the close handler, note that
removeListener('close', handler)andoff('close', handler)can still remove it. Whether this matters depends on your threat model — if onlyremoveAllListenersis called by the code paths you're guarding against, this is fine. Otherwise, consider wrappingoff/removeListenersimilarly for the'close'event.Also,
originalRemoveAllListenersis already.bind(backgroundWindow)on line 104, so the.call(backgroundWindow, ...)on lines 108 and 112 is redundant — you can calloriginalRemoveAllListeners(eventName)directly.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/actions/startBackgroundProcess.js` around lines 103 - 113, The override only protects removeAllListeners; to fully guard the close handler on backgroundWindow, also wrap/remove override removeListener and off so attempts to remove the 'close' handler are ignored or re-attached: intercept backgroundWindow.removeListener and backgroundWindow.off, check if eventName === 'close' (and optionally match handler === closeHandler) then no-op or re-attach closeHandler, otherwise delegate to the original bound functions; also simplify calls to originalRemoveAllListeners by invoking originalRemoveAllListeners(eventName) directly (remove the redundant .call(backgroundWindow, ...)) and keep references to the originals (originalRemoveAllListeners, originalRemoveListener, originalOff) when adding these overrides.
119-120: Consider documenting the distinction between the two global flags.
global.isBackgroundProcessActive(line 119) andglobal.backgroundProcessStarted(line 120) convey subtly different semantics (current state vs. ever-started). A brief inline comment clarifying each flag's purpose would help future maintainers avoid misuse.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/actions/startBackgroundProcess.js` around lines 119 - 120, Add concise inline comments where global.isBackgroundProcessActive and global.backgroundProcessStarted are set (in the startBackgroundProcess flow) explaining that isBackgroundProcessActive denotes the current run-time state (true while the background loop/process is running) and backgroundProcessStarted denotes an "ever-started" flag (true once the process has been initiated at least once), so maintainers know one is transient/current-state and the other is a one-time marker used for startup logic and metrics. Ensure comments are placed immediately above the two assignments and mention intended usage (e.g., toggled on stop vs. never reset) to prevent misuse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/main/actions/startBackgroundProcess.js`:
- Around line 73-80: The closeHandler currently sets event.returnValue = false
and returns false which are ineffective for a BrowserWindow 'close' event;
update closeHandler to rely solely on event.preventDefault() to cancel the close
and then hide the backgroundWindow if it's not destroyed (keep the existing
backgroundWindow.hide() call guarded by backgroundWindow.isDestroyed()); remove
the lines setting event.returnValue and the final return false so the handler
only calls event.preventDefault() and performs the hide action.
- Around line 103-113: The override only protects removeAllListeners; to fully
guard the close handler on backgroundWindow, also wrap/remove override
removeListener and off so attempts to remove the 'close' handler are ignored or
re-attached: intercept backgroundWindow.removeListener and backgroundWindow.off,
check if eventName === 'close' (and optionally match handler === closeHandler)
then no-op or re-attach closeHandler, otherwise delegate to the original bound
functions; also simplify calls to originalRemoveAllListeners by invoking
originalRemoveAllListeners(eventName) directly (remove the redundant
.call(backgroundWindow, ...)) and keep references to the originals
(originalRemoveAllListeners, originalRemoveListener, originalOff) when adding
these overrides.
- Around line 119-120: Add concise inline comments where
global.isBackgroundProcessActive and global.backgroundProcessStarted are set (in
the startBackgroundProcess flow) explaining that isBackgroundProcessActive
denotes the current run-time state (true while the background loop/process is
running) and backgroundProcessStarted denotes an "ever-started" flag (true once
the process has been initiated at least once), so maintainers know one is
transient/current-state and the other is a one-time marker used for startup
logic and metrics. Ensure comments are placed immediately above the two
assignments and mention intended usage (e.g., toggled on stop vs. never reset)
to prevent misuse.
… console
Summary by CodeRabbit
New Features
Bug Fixes